package com.j256.ormlite.hmos.apptools;

import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.dao.RuntimeExceptionDao;
import com.j256.ormlite.hmos.HmosConnectionSource;
import com.j256.ormlite.logger.Logger;
import com.j256.ormlite.logger.LoggerFactory;
import com.j256.ormlite.misc.IOUtils;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.DatabaseTableConfigLoader;
import com.j256.ormlite.utils.Constants;

import ohos.app.Context;
import ohos.data.DatabaseHelper;
import ohos.data.rdb.RdbStore;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.SQLException;


/**
 * OrmLiteRdbOpenHelper
 *
 */
public abstract class OrmLiteRdbOpenHelper extends DatabaseHelper {
    /**
     * logger
     */
    protected static final Logger logger = LoggerFactory.getLogger(OrmLiteRdbOpenHelper.class);

    /**
     * databaseName
     */
    public String databaseName;

    /**
     * databaseAlias
     */
    public String databaseAlias;
    /**
     * databaseVersion
     */
    public int databaseVersion;

    /**
     * HmosConnectionSource
     */
    public HmosConnectionSource connectionSource = null;
    protected boolean cancelQueriesEnabled;
    private volatile boolean isOpen = true;

    /**
     * OrmLiteRdbOpenHelper
     *
     * @param context Context
     * @param databaseName DatabaseName
     * @param databaseVersion DatabaseVersion
     * @param aliasName AliasName
     */
    public OrmLiteRdbOpenHelper(Context context, String databaseName, String aliasName, int databaseVersion) {
        super(context);
        this.databaseName = databaseName;
        this.databaseAlias = aliasName;
        this.databaseVersion = databaseVersion;
        if (null == databaseName) {
            logger.trace("{}: constructed OrmLiteRdbOpenHelper failure", this);
            return;
        }
        connectionSource = new HmosConnectionSource(this);

        logger.trace("{}: constructed ", this);
    }

    /**
     * OrmLiteRdbOpenHelper
     *
     * @param context Context
     * @param databaseName DatabaseName
     * @param databaseVersion DatabaseVersion
     * @param configFilePath  Configuration file to be loaded.
     */
    public OrmLiteRdbOpenHelper(Context context, String databaseName, int databaseVersion,
                                String configFilePath) {
        this(context, databaseName, databaseVersion, openFile(context, configFilePath));
    }

    /**
     * OrmLiteRdbOpenHelper
     *
     * @param context Context
     * @param databaseName databaseName
     * @param databaseVersion databaseVersion
     * @param stream Stream
     */
    public OrmLiteRdbOpenHelper(Context context, String databaseName, int databaseVersion,
                                InputStream stream) {
        super(context);
        this.databaseName = databaseName;
        this.databaseVersion = databaseVersion;
        if (stream == null) {
            return;
        }
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(stream), Constants.MAGIC_4096);
            stream = null;
            DaoManager.addCachedDatabaseConfigs(DatabaseTableConfigLoader.loadDatabaseConfigFromReader(reader));
        } catch (SQLException e) {
            throw new IllegalStateException("Could not load object config file", e);
        } finally {
            IOUtils.closeQuietly(reader);
            IOUtils.closeQuietly(stream);
        }
    }

    /**
     * What to do when your database needs to be created. Usually this entails creating the tables and loading any
     * initial data.
     *
     * <p>
     * <b>NOTE:</b> You should use the connectionSource argument that is passed into this method call or the one
     * returned by getConnectionSource(). If you use your own, a recursive call or other unexpected results may result.
     * </p>
     *
     * @param database         Database being created.
     * @param connectionSource To use get connections to the database to be created.
     */
    public abstract void onCreate(RdbStore database, ConnectionSource connectionSource);

    /**
     * What to do when your database needs to be updated. This could mean careful migration of old data to new data.
     * Maybe adding or deleting database columns, etc..
     *
     * <p>
     * <b>NOTE:</b> You should use the connectionSource argument that is passed into this method call or the one
     * returned by getConnectionSource(). If you use your own, a recursive call or other unexpected results may result.
     * </p>
     *
     * @param database         Database being upgraded.
     * @param connectionSource To use get connections to the database to be updated.
     * @param oldVersion       The version of the current database so we can know what to do to the database.
     * @param newVersion       The version that we are upgrading the database to.
     */
    public abstract void onUpgrade(RdbStore database, ConnectionSource connectionSource, int oldVersion,
                                   int newVersion);


    /**
     * Get the connection source associated with the helper.
     * @return the connection source associated with the helper.
     */
    public ConnectionSource getConnectionSource() {
        if (!isOpen) {
            // we don't throw this exception, but log it for debugging purposes
            logger.warn(new IllegalStateException(), "Getting connectionSource was called after closed");
        }
        return connectionSource;
    }

    /**
     * Close any open connections.
     */

    public void close() {
        connectionSource.close();
        /*
         * We used to set connectionSource to null here but now we just set the closed flag and then log heavily if
         * someone uses getConectionSource() after this point.
         */
        isOpen = false;
    }

    /**
     *Check if open
     * @return boolean
     */
    public boolean isOpen() {
        return isOpen;
    }

    public int getDatabaseVersion() {
        return this.databaseVersion;
    }

    /**
     * Get a DAO for our class. This uses the {@link DaoManager} to cache the DAO for future gets.
     * <p>
     * NOTE: This routing does not return Dao&lt;T, ID&gt; because of casting issues if we are assigning it to a custom DAO.
     * Grumble.
     * </p>
     * @param clazz DAO for class
     * @param <D> DAO
     * @param <T> Generic
     * @return castDao
     * @throws SQLException
     */
    public <D extends Dao<T, ?>, T> D getDao(Class<T> clazz) throws SQLException {
        // special reflection fu is now handled internally by create dao calling the database type
        Dao<T, ?> dao = DaoManager.createDao(getConnectionSource(), clazz);
        @SuppressWarnings("unchecked")
        D castDao = (D) dao;
        return castDao;
    }

    /**
     * Get a RuntimeExceptionDao for our class. This uses the {@link DaoManager} to cache the DAO for future gets.
     *
     * <p>
     * NOTE: This routing does not return RuntimeExceptionDao&lt;T, ID&gt; because of casting issues if we are assigning it to
     * a custom DAO. Grumble.
     * </p>
     * @param clazz Get a RuntimeExceptionDao for our class
     * @param <D> DaoManager
     * @param <T> generics
     * @return castDao
     */
    public <D extends RuntimeExceptionDao<T, ?>, T> D getRuntimeExceptionDao(Class<T> clazz) {
        try {
            Dao<T, ?> dao = getDao(clazz);
            @SuppressWarnings({"unchecked", "rawtypes"})
            D castDao = (D) new RuntimeExceptionDao(dao);
            return castDao;
        } catch (SQLException e) {
            throw new RuntimeException("Could not create RuntimeExcepitionDao for class " + clazz, e);
        }
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode());
    }

    private static InputStream openFileId(Context context, int fileId) {
    	return null;
    }

    private static InputStream openFile(Context context, String configFile) {
        try {
            return context.getResourceManager().getRawFileEntry(configFile).openRawFile();
        } catch (IOException e) {
            throw new IllegalArgumentException("Could not open config file " + configFile, e);
        }
    }
}
